﻿/************************************************************************
 * CoreDES - A small and core library for discrete event systems
 * Copyright (C) 2012-2013 Ali Khalili (khalili_DOT_ir_@SIGN_gmail_DOT_com)
 * **********************************************************************
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License (LGPL) published 
 * by the Free Software Foundation; either version 2.1 of the License, or (at your
 * option) any later version. This program is distributed without any
 * warranty. See the GNU Lesser General Public License for more details. 
 * ***********************************************************************
 * File         :     CoreDES\Execution\CompilerHelper.cs
 * Description  :     the base class for all class which wants to analysis a DES model. 
 *                    It facilitate the compilation of the model to generate an 
 *                    executable object from the model
 * Change log   :     -oct 2013: major modifications
 *                    -2010: initial version (it originaly belongs to simgine library)
 * To Do        :
 ************************************************************************/
using System;
using System.Collections.Generic;
using System.CodeDom.Compiler;
using System.Reflection;
using System.IO;
using CoreDES.Base;

namespace CoreDES.Execution
{
    /// <summary>
    /// The base class for all class which wants to analysis a DES model. It facilitate the compilation of the model
    /// to generate an executable object from the model
    /// </summary>
    public class CompilerHelper
    {
        #region -=[ CONFIGURATION ]=-

        /// <summary>
        /// Save list of errors?
        /// </summary>
        bool saveErrors;

        /// <summary>
        /// Output directory for list of errors
        /// </summary>
        string SavingPathErrors;

        #endregion

        bool finilized = false;

        /// <summary>
        /// List of referenced assemblies/libraries (for example, .dll files)
        /// </summary>
        List<string> libs;

        /// <summary>
        /// List of source code to be attached (.cs files)
        /// </summary>
        List<string> sources;

        /// <summary>
        /// Generated assembly
        /// </summary>
        Assembly loAssembly;

       
        /// <summary>
        /// Constructor, add EnGine Library to the code automatically
        /// </summary>
        /// <param name="className">The name of generated class</param>
        /// <param name="libs">DLLs and libraries to be attached</param>
        /// <param name="sources">Path of other sources</param>
        public CompilerHelper(List<string> libs = null, List<string> sources = null)
        {
			if(!LibSetting.IsInitialized)
			{
				Console.Write("Warning. CoreDES setting was not initialized! Trying to initialize it...");
				LibSetting.Init();
				Console.WriteLine("Done!");
			}
			saveErrors=LibSetting.SaveLog;
			SavingPathErrors=LibSetting.LogPath;
			
            this.sources = sources;
            if (this.sources == null)
                this.sources = new List<string>();

            this.libs = libs;
            if (this.libs == null)
                this.libs = new List<string>();
            
            //* the system.dll should always be included
            if (!this.libs.Contains("System.dll"))
                this.libs.Add("System.dll");
        }

        /// <summary>
        /// Add some libraries to be used in code compilation
        /// </summary>
        /// <param name="lib">the library to be used.</param>
        public void AddLibrary(string lib)
        {
            if (finilized)
                throw new Exception("After compilation, you cannot add any library!");
            libs.Add(lib);
        }

        /// <summary>
        /// Add some header files to be used in code compilation
        /// </summary>
        /// <param name="hdr">the header file to be used</param>
        public void AddSource(string hdr)
        {
            if (finilized)
                throw new Exception("After compilation, you cannot add any source file!");
            sources.Add(hdr);
        }


        /// <summary>
        /// Compile a code as a string and generate the Assembly
        /// </summary>
        /// <param name="Code">Input string contating the code to be compiled</param>
        /// <returns>Generated Assembly</returns>
        public virtual Assembly Compile(CompilerParameters loParameters = null)
        {
            //* make a compiler provider
            CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");
            //[old version]:ICodeCompiler loCompiler = new CSharpCodeProvider().CreateCompiler();

            if (loParameters == null)
                loParameters = new CompilerParameters();

            //* add all libraries
            foreach (string lib in libs)
                loParameters.ReferencedAssemblies.Add(lib);

            loParameters.GenerateInMemory = false;
            List<string> sources = new List<string>();
            foreach (string file in this.sources)
            {
                if (!File.Exists(file))
                {
                    throw new Exception("File " + file + " Does not exist!");
                }
                sources.Add(File.ReadAllText(file));
            }

            //* make the compiler and compile every thing
            CompilerResults loCompiled = provider.CompileAssemblyFromSource(loParameters, sources.ToArray());
            //[old version]:CompilerResults loCompiled = loCompiler.CompileAssemblyFromSourceBatch(loParameters, sources.ToArray());

            //* check for error
            //string errorList;
            int numberOfErrors = 0;
            if (loCompiled.Errors.HasErrors)
            {
                string lcErrorMsg = "";
                lcErrorMsg = loCompiled.Errors.Count.ToString() + " Errors:";
                for (int x = 0; x < loCompiled.Errors.Count; x++)
                    if (!loCompiled.Errors[x].IsWarning)
                    {
                        lcErrorMsg += "\r\n" + loCompiled.Errors[x].FileName + ", Line: " + loCompiled.Errors[x].Line.ToString() +
                            " - " + loCompiled.Errors[x].ErrorText;
                        numberOfErrors++;
                    }
                //errorList = lcErrorMsg;

                if (saveErrors)
                    File.WriteAllText(SavingPathErrors, ("\n*****************List of Errors*****************\n" + lcErrorMsg));
                return null;
            }
            loAssembly = loCompiled.CompiledAssembly;
            return loCompiled.CompiledAssembly;
        }

        /// <summary>
        /// Generate an executable object from the assembly (handlers should be set here)
        /// </summary>
        /// <param name="loAssembly">Input assembly</param>
        /// <returns>Generated executable object</returns>
        public object GenerateExecutable(string className)
        {
            if (loAssembly != null)
            {
                object loObject = loAssembly.CreateInstance(className);
                return loObject;
            }
            else
                return null;
        }


        /// <summary>
        /// Get type of a given string
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public Type GetTypeof(string type)
        {
            if (loAssembly != null)
                return loAssembly.GetType(type);
            else
                return null;
        }

        /// <summary>
        /// Generated assembly
        /// </summary>
        public Assembly CompiledAssembly
        {
            get
            {
                return this.loAssembly;
            }
        }

        /// <summary>
        /// Clear configuration. Now, you can use it again for another compilation!
        /// </summary>
        public void Reset()
        {
            loAssembly = null;
            libs.Clear();
            sources.Clear();
            finilized = false;
        }
    }
}
